package com.lqwu.mallchatserver.websocket.service.impl;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.lqwu.mallchatserver.common.event.UserOnlineEvent;
import com.lqwu.mallchatserver.user.model.entry.User;
import com.lqwu.mallchatserver.user.model.enums.ActiveStatusEnum;
import com.lqwu.mallchatserver.user.model.enums.RoleEnum;
import com.lqwu.mallchatserver.user.service.LoginService;
import com.lqwu.mallchatserver.user.service.RoleService;
import com.lqwu.mallchatserver.user.service.UserService;
import com.lqwu.mallchatserver.websocket.NettyUtil;
import com.lqwu.mallchatserver.websocket.model.dto.WSChannelExtraDTO;
import com.lqwu.mallchatserver.websocket.model.vo.response.WsBaseResponse;
import com.lqwu.mallchatserver.websocket.service.WebSocketService;
import com.lqwu.mallchatserver.websocket.service.adapter.WebSocketAdapter;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.SneakyThrows;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 处理 WebSocket 的接口实现类
 */
@Service
public class WebSocketServiceImpl implements WebSocketService {
    @Resource
    @Lazy
    private WxMpService wxMpService;
    @Resource
    private LoginService loginService;
    @Resource
    private UserService userService;
    @Resource
    private ApplicationEventPublisher publisher;
    @Resource
    private RoleService roleService;
    @Resource
    private ThreadPoolTaskExecutor websocketExecutor;

    public static final int MAXIMUM_SIZE = 10000;// 本地缓存最大容量
    public static final Duration DURATION = Duration.ofHours(1);// 过期时间

    /**
     * 管理所有在线用户连接的 map （包含-登录态 、游客）
     */
    private static ConcurrentHashMap<Channel, WSChannelExtraDTO> ONLINE_WS_MAP = new ConcurrentHashMap<>();

    /**
     * 存储等待用户登录code 和连接 的本地缓存 map
     */
    private static Cache<Integer,Channel> WAIT_LOGIN_MAP = Caffeine.newBuilder()
            .maximumSize(MAXIMUM_SIZE).expireAfterWrite(DURATION).build();

    /**
     * 处理用户登录请求，需要返回一张带code的二维码
     * @param channel
     */
    @SneakyThrows
    @Override
    public void handleLoginReq(Channel channel) {
        // 1.生成随机的码
        Integer code = generateLoginCode(channel);
        // 2.找微信申请带参二维码
        WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) DURATION.getSeconds());
        // 3.把二维码推给前端
        sendMsg(channel, WebSocketAdapter.buildResp(wxMpQrCodeTicket));
    }

    /**
     * 向前端发消息
     * @param channel
     * @param response
     */
    private void sendMsg(Channel channel, WsBaseResponse<?> response) {
        // 需发成 ws 才能接收
        channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(response)));
    }

    /**
     * 根据连接生成登陆码，并将连接和登录码关联
     * @param channel
     * @return
     */
    private Integer generateLoginCode(Channel channel) {
        Integer code;
        do {
            // 随机生成范围内的code
            code = RandomUtil.randomInt(Integer.MAX_VALUE);
            // 将WAIT_LOGIN_MAP转Map，如果不存在就添加
        }while (ObjectUtil.isNotNull(WAIT_LOGIN_MAP.asMap().putIfAbsent(code,channel)));
        return code;
    }

    /**
     * 处理所有ws连接的事件
     * @param channel
     */
    @Override
    public void connect(Channel channel) {
        // 将连接添加到在线 map 中
        // 先保存空对象，在登录时再把 uid 填上
        ONLINE_WS_MAP.put(channel,new WSChannelExtraDTO());
    }

    /**
     * 处理ws断开连接的事件
     * @param channel
     */
    @Override
    public void removed(Channel channel) {
        ONLINE_WS_MAP.remove(channel);
        // todo 推送用户下线消息
    }

    /**
     * 主动认证登录
     * @param channel
     * @param data
     */
    @Override
    public void authorize(Channel channel, String data) {
        // 1.调用登录模块，获取uid
        Long uid = loginService.getValidUid(data);
        // 2.判断登录是否成功
        if (ObjUtil.isNotNull(uid)){
            User user = userService.getById(uid);
            // 修改ONLINE_WS_MAP中对应channel登录态
            loginSuccess(channel,user,data);
        }else {
            // 发送错误消息给前端
            sendMsg(channel, WebSocketAdapter.buildInvalidTokenResp());
        }
    }

    /**
     * 登录成功，更新登录态
     * @param channel
     * @param user
     * @param token
     */
    private void loginSuccess(Channel channel, User user, String token) {
        // 1.保存 channel 连接的对应的 uid
        WSChannelExtraDTO wsChannelExtraDTO = ONLINE_WS_MAP.get(channel);
        wsChannelExtraDTO.setUid(user.getId());
        // 2.发送登录成功消息给前端
        sendMsg(channel, WebSocketAdapter.buildResp(user,token,roleService.hasPower(user.getId(), RoleEnum.CHAT_MANAGER.getId())));
        // 3.发送用户上线的消息
        // 设置最后上线时间,在线状态。。。。。
        user.setActiveStatus(ActiveStatusEnum.ONLINE.getType());
        user.setLastOptTime(new Date());
        // ip 相关信息
        user.refreshIp(NettyUtil.getAttr(channel, NettyUtil.IP));
        // 4.发布用户上线事件
        publisher.publishEvent(new UserOnlineEvent(this,user));
    }

    /**
     * 扫码用户登录成功通知,清除本地Cache中的loginCode和channel的关系
     */
    @Override
    public Boolean scanLoginSuccess(Integer loginCode, Long uid) {
        // 1.删除等待扫码用户登录的code和channel的关联
        // 获取channel连接
        Channel channel = WAIT_LOGIN_MAP.getIfPresent(loginCode);
        // 确认连接在机器上
        if (channel == null){
            return false;
        }
        // 删掉本地缓存中的loginCode和channel的关联
        WAIT_LOGIN_MAP.invalidate(loginCode);

        // 2.调用登录模块，获取token
        String token = loginService.login(uid);
        // 4.发送消息给前端
        loginSuccess(channel,userService.getById(uid),token);
        return true;
    }

    /**
     * 通知用户扫码成功
     * @param loginCode
     */
    @Override
    public Boolean scanSuccess(Integer loginCode) {
        return null;
    }

    /**
     * 等待用户授权
     * @param code
     */
    @Override
    public void waitAuthorize(int code) {
        // 1.获取code对应的channel
        Channel channel = WAIT_LOGIN_MAP.getIfPresent(code);
        if (channel == null){
            return;
        }
        // 2.发送消息给前端
        sendMsg(channel, WebSocketAdapter.buildResp());
    }

    @Override
    public void sendToAllOnline(WsBaseResponse<?> wsBaseResp, Long skipUid) {

    }

    /**
     * 推动消息给所有在线的人
     * @param wsBaseResp 发送的消息体
     */
    @Override
    public void sendToAllOnline(WsBaseResponse<?> wsBaseResp) {
        ONLINE_WS_MAP.forEach((channel, ext) -> {
            websocketExecutor.execute(() ->{
                sendMsg(channel, wsBaseResp);
            });
        });
    }

    @Override
    public void sendToUid(WsBaseResponse<?> wsBaseResp, Long uid) {

    }
}
